
package com.trydofor.id.body.shell;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.trydofor.aloe.exception.Exceptions;
import com.trydofor.id.byta.Bytable;
import com.trydofor.id.byta.ByteMeta;
import com.trydofor.id.byta.Bytes;
import com.trydofor.id.doer.shell.ArgHelp;
import com.trydofor.id.doer.shell.Command;

/**
 * command usage
 * 
 * @author : Shi Rongjiu (www.trydofor.com)
 */
public class UsageReplyBody implements Bytable<UsageReplyBody> {
    
    private final Map<String, Command> commands = new HashMap<String, Command>();
    
    /**
     * constructor
     */
    public UsageReplyBody() {
    }
    
    /**
     * add a command
     * 
     * @param name the command name
     * @param command the command
     */
    public void add(String name, Command command) {
        commands.put(name, command);
    }
    
    /**
     * get all command
     * 
     * @return the view of commands
     */
    public Map<String, Command> getAll() {
        return commands;
    }
    
    /**
     * {@inheritDoc}
     */
    public byte[] toBytes() throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            bos.write(Bytes.toBytes(commands.size())); // command
            for (String name : commands.keySet()) {
                Command cmd = commands.get(name);
                bos.write(Bytes.toMetaBytes(name));
                bos.write(Bytes.toMetaBytes(cmd.getAlias()));
                bos.write(Bytes.toMetaBytes(cmd.getUsage()));
                
                Collection<ArgHelp> args = cmd.getArgumentAll(); // argument
                bos.write(Bytes.toBytes(args.size()));
                for (ArgHelp arg : args) {
                    bos.write(Bytes.toMetaBytes(arg.getName()));
                    bos.write(arg.isRequired() ? 1 : 0);
                    bos.write(arg.isEditable() ? 1 : 0);
                    bos.write(Bytes.toMetaBytes(arg.getManual()));
                    bos.write(Bytes.toMetaBytes(arg.getPattern()));
                    bos.write(Bytes.toMetaBytes(arg.getDefault()));
                    
                    List<String> opts = arg.getOptions(); // option
                    bos.write(Bytes.toBytes(opts.size()));
                    for (String string : opts) {
                        bos.write(Bytes.toMetaBytes(string));
                    }
                }
            }
        }
        catch (IOException e) {
            Exceptions.throwFakeException(e);
        }
        return bos.toByteArray();
    }
    
    /**
     * {@inheritDoc}
     */
    public UsageReplyBody ofBytes(byte[] bytes, int offset, int length) throws Exception {
        ByteMeta<UsageReplyBody> bm = ofBytes(bytes, offset);
        return (bm.byteSize() != length) ? null : bm.instance();
    }
    
    /**
     * {@inheritDoc}
     */
    public ByteMeta<UsageReplyBody> ofBytes(byte[] bytes, int offset) throws Exception {
        UsageReplyBody inst = null;
        int size = 0;
        boolean done = false;
        top:
        while (!done) {
            int cmdSize = Bytes.toInt(bytes, offset, Bytes.BYTES_INT);
            if (cmdSize < 0) break top;
            size += Bytes.BYTES_INT;
            
            inst = new UsageReplyBody();
            for (int i = 0; i < cmdSize; i++) { // command
            
                ByteMeta<String> name = Bytes.ofMetaBytes(bytes, offset + size);
                if (name.byteSize() <= 0) break top;
                size += name.byteSize();
                
                ByteMeta<String> alias = Bytes.ofMetaBytes(bytes, offset + size);
                if (alias.byteSize() <= 0) break top;
                size += alias.byteSize();
                
                ByteMeta<String> usage = Bytes.ofMetaBytes(bytes, offset + size);
                if (usage.byteSize() <= 0) break top;
                size += usage.byteSize();
                
                Command cmd = new Command(alias.instance(), usage.instance());
                
                int argSzie = Bytes.toInt(bytes, offset + size, Bytes.BYTES_INT);
                if (argSzie < 0) break top;
                size += Bytes.BYTES_INT;
                
                for (int j = 0; j < argSzie; j++) { // argument
                    ByteMeta<String> aName = Bytes.ofMetaBytes(bytes, offset + size);
                    if (aName.byteSize() <= 0) break top;
                    size += aName.byteSize();
                    
                    boolean aRequired = bytes[offset + size] == 1;
                    size++;
                    
                    boolean aEditable = bytes[offset + size] == 1;
                    size++;
                    
                    ByteMeta<String> aManual = Bytes.ofMetaBytes(bytes, offset + size);
                    if (aManual.byteSize() <= 0) break top;
                    size += aManual.byteSize();
                    
                    ByteMeta<String> aPattern = Bytes.ofMetaBytes(bytes, offset + size);
                    if (aPattern.byteSize() <= 0) break top;
                    size += aPattern.byteSize();
                    
                    ByteMeta<String> aDefault = Bytes.ofMetaBytes(bytes, offset + size);
                    if (aDefault.byteSize() <= 0) break top;
                    size += aDefault.byteSize();
                    
                    ArgHelp arg = new ArgHelp(aName.instance());
                    arg.setManual(aManual.instance());
                    arg.setRequired(aRequired);
                    arg.setEditable(aEditable);
                    arg.setPattern(aPattern.instance());
                    arg.setDefault(aDefault.instance());
                    
                    int optSzie = Bytes.toInt(bytes, offset + size, Bytes.BYTES_INT); // option
                    if (optSzie < 0) break top;
                    size += Bytes.BYTES_INT;
                    
                    for (int k = 0; k < optSzie; k++) {
                        ByteMeta<String> opt = Bytes.ofMetaBytes(bytes, offset + size);
                        if (opt.byteSize() <= 0) break top;
                        size += opt.byteSize();
                        arg.addOption(opt.instance());
                    }
                    
                    cmd.addArgument(arg);
                }
                //
                inst.add(name.instance(), cmd);
            }
            done = true;
        }
        //
        if (!done) {
            inst = null;
            size = 0;
        }
        
        return ByteMeta.of(inst, size);
    }
}
